#ifndef CORE_IO_H
#define CORE_IO_H

#include "core.h"

#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <QDomDocument>
#include <QStringList>
#include "recent_docs_manager.h"

class CoreIO
{
public:

    static Song* OpenSong(const QString& filename)
    {
        QFileInfo fileInfo(filename);

        QString suffix = fileInfo.suffix();
        if(suffix == "mps")
        {
            return LoadMPSFile(fileInfo);
        }else
        {
            return CreateSongObject(fileInfo);
        }
    }

    // saves the song currently opened by the player
    static bool SaveSong(QString& filename, Song* song )
    {
        if(song == 0) return false;

        QDomDocument doc("MPS");
        QDomElement root = doc.createElement("MPS");
        doc.appendChild(root);

        // saves the general infos about the song
        AddChild(doc, root, "song_complete_name", song->songFilename);
        AddChild(doc, root, "song_name", song->songName);
        AddChild(doc, root, "song_format", song->format);
        AddChild(doc, root, "song_length", QString::number(song->length));
        AddChild(doc, root, "song_default_pitch", QString::number(song->defaultPitch));

        // saves the practice segments
        QDomElement segments = doc.createElement("segments");
        AddChild( doc, segments, "ps_count", QString::number( song->segments.size() ) );
        for(int i=0; i<song->segments.size(); i++)
        {
            PracticeSegment ps = song->segments.at( i );
            segments.appendChild( CreateSegmentElement( doc, i, ps ) );
        }
        root.appendChild(segments);

        // saves the loops
        QDomElement loops = doc.createElement( "loops" );
        AddChild( doc, loops, "loops_count", QString::number( song->loops.size() ) );
        for( int i = 0; i < song->loops.size(); i++ )
        {
            Loop l = song->loops.at( i );
            loops.appendChild( CreateLoopElement( doc, i, l ) );
        }
        root.appendChild( loops );

        // saves the dom created
        QFile file(filename);
        if( !file.open( QIODevice::WriteOnly ) )
          return false;

        QTextStream ts( &file );
        ts << doc.toString();
        file.close();

        song->mpsFileName = filename;
        return true;
    }

    static RecentDocsManager* docsManager;

private:
    static Song* LoadMPSFile(QFileInfo& fileInfo)
    {
        QDomDocument doc( "MPS" );
        QString filename = fileInfo.filePath();

        // loads the DOM tree into the QDomDocument object
        QFile file( filename );
        if( !file.open( QIODevice::ReadOnly ) )
        {
            return 0;
        }

        QString err;
        int errLine;
        int errCol;
        if( !doc.setContent( &file, false, &err, &errLine, &errCol) )
        {
            qDebug( "Error on line %d, column %d: %s", errLine, errCol, qPrintable( err ) );
            file.close();
            return 0;
        }
        file.close();

        // loads general informations
        Song* song = new Song();
        song->mpsFileName = filename;
        song->songFilename = ReadText( doc, "song_complete_name" );
        song->songName = ReadText( doc, "song_name" );
        song->format = ReadText( doc, "song_format" );
        song->length = ReadText( doc, "song_length" ).toInt();
        song->defaultPitch = ReadText( doc, "song_default_pitch" ).toInt();

        // loads the practice segments
        int psCount = ReadText( doc, "ps_count" ).toInt();
        for( int i=0; i<psCount; i++ )
        {
            LoadPracticeSegment( doc, i, song );
        }

        // loads the loops
        int loopsCount = ReadText( doc, "loops_count" ).toInt();
        for( int i=0; i<loopsCount; i++)
        {
            LoadLoop( doc, i, song );
        }

        return song;
    }

    // creates an unsaved song object
    static Song* CreateSongObject(QFileInfo& fileInfo)
    {
        Song* song = new Song();
        song->defaultPitch = 0;
        song->mpsFileName = "unknown";
        song->songFilename = fileInfo.filePath();
        song->format = fileInfo.suffix();
        song->songName = fileInfo.baseName();
        song->length = 0;

        PracticeSegment ps;
        ps.start = 0;
        ps.name = "Practice Segment 0";
        ps.end = song->length;
        ps.pitch = 0;
        ps.tempo = 1.0;
        ps.iterations = -1;
        ps.active = false;
        ps.enabled = true;
        song->segments.append(ps);

        return song;
    }

    // adds a the element <tagName>text</tagName> to the parent element
    static void AddChild(QDomDocument& doc, QDomElement& parent, const QString& tagName, const QString& text)
    {
        QDomText domText = doc.createTextNode(text);
        QDomElement el = doc.createElement(tagName);
        el.appendChild(domText);
        parent.appendChild(el);
    }

    // creates a QDomElement that encapsulates a PracticeSegment object
    static QDomElement CreateSegmentElement(QDomDocument& doc, int segmentIndex, PracticeSegment& segment)
    {
        QDomElement el = doc.createElement("ps_" + QString::number(segmentIndex));
        AddChild(doc, el, "name", segment.name);
        AddChild(doc, el, "start", QString::number(segment.start) );
        AddChild( doc, el, "end", QString::number(segment.end) );
        AddChild( doc, el, "tempo", QString::number(segment.tempo) );
        AddChild( doc, el, "pitch", QString::number(segment.pitch) );
        AddChild( doc, el, "iterations", QString::number( segment.iterations ) );
        AddChild( doc, el, "active", segment.active? "true" : "false" );
        AddChild( doc, el, "enabled", segment.enabled? "true" : "false" );
        return el;
    }

    // creates a QDomElement that encapsulates a Loop object
    static QDomElement CreateLoopElement(QDomDocument& doc, int loopIndex, Loop& loop)
    {
        QDomElement el = doc.createElement("loop_" + QString::number(loopIndex));
        AddChild(doc, el, "name", loop.name);
        AddChild(doc, el, "start", QString::number(loop.start) );
        AddChild( doc, el, "end", QString::number(loop.end) );
        AddChild( doc, el, "iterations", QString::number(loop.iterations) );
        AddChild( doc, el, "active", loop.active? "true" : "false" );
        return el;
    }

    // reads the text in the element specified by tagName
    static QString ReadText( QDomDocument& doc, const QString& tagName )
    {
        QDomNodeList nList = doc.elementsByTagName( tagName );
        return GetText(nList.at(0));
    }

    // loads a practice segment
    static void LoadPracticeSegment( QDomDocument& doc, int psIndex, Song* song )
    {
        QString groupName = "ps_" + QString::number( psIndex );
        QDomNodeList nList = doc.elementsByTagName( groupName );
        QDomNodeList psList = nList.at(0).childNodes();

        PracticeSegment ps;
        ps.name = GetText( psList.at(0) );                  // name
        ps.start = GetText( psList.at(1) ).toInt();         // start
        ps.end = GetText( psList.at(2) ).toInt();           // end
        ps.tempo = GetText( psList.at(3) ).toFloat();       // tempo
        ps.pitch = GetText( psList.at(4) ).toInt();         // pitch
        ps.iterations = GetText( psList.at(5) ).toInt();    // iterations
        ps.active = ( GetText( psList.at(6) ) == "true" );  // active
        ps.enabled = ( GetText( psList.at(7) ) == "true" ); // enabled

        song->segments.append( ps );
    }

    // loads a loop
    static void LoadLoop( QDomDocument& doc, int loopIndex, Song* song )
    {
        QString groupName = "loop_" + QString::number( loopIndex );
        QDomNodeList nList = doc.elementsByTagName( groupName );
        QDomNodeList loopList = nList.at(0).childNodes();

        Loop l;
        l.name = GetText( loopList.at(0) );                 // name
        l.start = GetText( loopList.at(1) ).toInt();        // start
        l.end = GetText( loopList.at(2) ).toInt();          // end
        l.iterations = GetText( loopList.at(3) ).toInt();   // iterations
        l.active = ( GetText( loopList.at(4) ) == "true" ); // active

        song->loops.append( l );
    }

    // reads the text in a QDomNode
    static QString GetText(QDomNode n)
    {
        QDomElement* el = (QDomElement*)(&n);
        return el->text();
    }
};

#endif // CORE_IO_H
